package liquidfs.data.fs;

import java.io.*;
import java.util.*;

import com.thoughtworks.xstream.annotations.*;
import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.data.ex.*;
import liquidfs.data.ex.FileNotFoundException;
import liquidfs.data.fs.util.*;
import liquidfs.data.ops.*;

@XStreamAlias("remote-liquid-object")
public class RemoteLiquidObject extends AbstractLiquidObject implements LiquidFile, LiquidDirectory {

   private LiquidFileSystem fs;
   private LiquidMetadataCache metaCache;
   private LiquidDataCache dataCache = null;
   private LiquidContentsCache contentsCache = null;
   
   public RemoteLiquidObject (LiquidFileSystem fs, String path) {
      this.fs = fs;
      this.metaCache = null;
   }
   
   public RemoteLiquidObject (LiquidFileSystem fs, LiquidMetadataCache cache) {
      this.fs = fs;
      this.metaCache = cache;
   }
   
   protected LiquidFileSystem getFileSystem () {
      return this.fs;
   }
   
   @Override
   public void refresh () {
      this.metaCache.clear();
      this.contentsCache = null;
   }
   
   public void loadObjectInformation () {
      QueryObjectInformationOperation op = new QueryObjectInformationOperation(fs.getLocalDevice(), this.getPath());
      Result<LiquidObject> cacheResult = fs.process(op);
      
      try {
         this.metaCache = new LiquidMetadataCache(cacheResult.getData());
      }
      catch (FileSystemException e) {
         this.metaCache.clear();
         throw e;
      }
   }
   
   @Override
   public String getPath() {
      return this.metaCache.getPath();
   }

   @Override
   public String getName() {
      return this.metaCache.getName();
   }

   @Override
   public boolean isDirectory() {
      
      // If we haven't cached this, update the cache
      if (metaCache.isDirectory() == null) {
         this.loadObjectInformation();
      }
      
      return metaCache.isDirectory().booleanValue();
   }
   
   @Override
   public LiquidDirectory asDirectory () {
      return this;
   }
   
   @Override 
   public LiquidFile asFile () {
      return this;
   }

   @Override
   public long getSize() {
      
      if (metaCache.getSize() == null) {
         this.loadObjectInformation();
      }
      
      return metaCache.getSize().longValue();
      
   }
   
   @Override
   public long getFileSize() {
      
      if (!this.isDirectory())
         return 0;
      
      long totalSize = 0;
      List<LiquidObject> contents = this.getContents();
      
      for (LiquidObject object : contents) {
         totalSize += object.getSize();
      }
      
      return totalSize;
   }
   
   @Override
   public long getLocalFileSize() {
      // Remote files/directories never have a local size
      return 0;
   }
   
   @Override
   public Metadata getMetadata() {
      if (metaCache.getMetadata() == null) {
         this.loadObjectInformation();
      }
      
      return metaCache.getMetadata();
   }

   @Override
   public Date getCreationDate() {
      return this.getMetadata().getCreationDate();
   }

   @Override
   public Date getModificationDate() {
      return this.getMetadata().getModificationDate();
   }

   @Override
   public Date getLastAccessedDate() {
      return this.getMetadata().getLastAccessedDate();
   }

   @Override
   public List<LiquidObject> getContents() {
      
      if (this.contentsCache == null) {
         // Do this through an operation
         QueryDirectoryContentsOperation op = new QueryDirectoryContentsOperation (this.getFileSystem().getLocalDevice(), this.getPath());
         Result<List<LiquidObject>> result = this.getFileSystem().process(op, null, null);
         
         contentsCache = new LiquidContentsCache(this.getPath(), result.getData());
      }
      
      // Return the list
      return contentsCache.getContents();
   }

   @Override
   public List<LocalLiquidObject> getLocalContents() {
      // There are no local contents for a remote directory, so return an empty list
      return new ArrayList<LocalLiquidObject>(1);
   }

   @Override
   public boolean exists() {
      
      // Determine if it exists based on whether we have a valid cache.
      // If we do not, try to get one.
      // If that fails with a FileNotFound, then it doesn't exist.
      
      if (metaCache.isDirectory() != null) {
         try {
            this.loadObjectInformation();
         }
         catch (FileNotFoundException e) {
            return false;
         }
      }
      
      return true;
   }
   
   @Override
   public void open (OpenMode mode) {
      
      // Create a new cache
      this.dataCache = new LiquidDataCache(
         this.getFileSystem(),
         this
      );
      
      // Open the cache
      this.dataCache.open(mode);
      
   }
   
   @Override
   public void close () {
      this.dataCache.close();
      this.dataCache = null;
   }
   
   @Override
   public boolean isOpen () {
      return this.dataCache != null;
   }
   
   @Override
   public void truncate () {
      
      if (this.dataCache == null) {
         throw new FileSystemException ("File " + this.getPath() + " is not open.");
      }
      
      this.dataCache.truncate();
   }
   
   @Override
   public void setLength (long length) {
      
      if (this.dataCache == null) {
         throw new FileSystemException ("File " + this.getPath() + " is not open.");
      }
      
      this.dataCache.setLength(length);
   }
   
   @Override
   public InputStream getInputStream() {
      return this.getInputStream(0);
   }
   
   @Override
   public InputStream getInputStream(long position) {
      if (this.dataCache == null) {
         throw new FileSystemException("File " + this.getPath() + " not opened.");
      }
      
      return this.dataCache.getInputStream(position);
   }
   
   @Override 
   public OutputStream getOutputStream() {
      return this.getOutputStream(0);
   }
   
   @Override
   public OutputStream getOutputStream(long position) {
      if (this.dataCache == null) {
         throw new FileSystemException("File " + this.getPath() + " not opened.");
      }
      
      return this.dataCache.getOutputStream(position);
   }

   @Override
   public List<PeerDevice> getStorageDevices() {
      
      if (this.metaCache.getStorageDevices() == null) {
         QueryStorageDevicesOperation op = new QueryStorageDevicesOperation(this.getFileSystem().getLocalDevice(), this.getPath());
         Result<List<PeerDevice>> result = this.getFileSystem().process(op);
         
         this.metaCache.setStorageDevices(result.getData());
      }
      
      return this.metaCache.getStorageDevices();
   }
}
